/*! \file
 * \brief Enter a brief description here.
 *
 * Enter a detailed description here. (optional)
 * \author Author (Ivo Wingelaar)
 * \date Date (2011)
 * \version Version (0.0.1)
 * \copyright Copyright (GNU LGPL v3)
 */

#include "LynxTexture_C.hpp"
#include "LynxVideoManager_C.hpp"
#include "LynxImage_C.hpp"

namespace Lynx
{

//! normal map lookup 32 bit version
inline float nml32(int x, int y, int pitch, int height, int32_t* p)
{
    if(x < 0) x = pitch-1;
    if(x >= pitch) x = 0;
    if(y < 0) y = height-1;
    if(y >= height) y = 0;
    return (float)(((p[(y * pitch) + x])>>16) & 0xff);
}

//! normal map lookup 16 bit version
inline float nml16(int x, int y, int pitch, int height, int16_t* p)
{
    if(x < 0) x = pitch-1;
    if(x >= pitch) x = 0;
    if(y < 0) y = height-1;
    if(y >= height) y = 0;

    return (float) get_average(p[(y * pitch) + x]);
}

//! constructor for usual textures
Texture_C::Texture_C(Image* origImage, const std::string name, void* mipmapData, VideoManager_C* driver) :
    mColorFormat(ECF_A8R8G8B8),
    mDriver(driver),
    mImage(0),
    mMipImage(0),
    mTextureName(0),
    mInternalFormat(GL_RGBA),
    mPixelFormat(GL_BGRA_EXT),
    mPixelType(GL_UNSIGNED_BYTE),
    mIsRenderTarget(false),
    mAutomaticMipmapUpdate(false),
    mReadOnlyLock(false),
    mKeepImage(true)
{
    mHasMipMaps = mDriver->get_texture_creation_flag(ETCF_CREATE_MIP_MAPS);
    getImageValues(origImage);

    glGenTextures(1, &mTextureName);

    if(mImageSize == mTextureSize)
    {
        mImage = new Image_C(mColorFormat, mImageSize);
        origImage->copy_to(mImage);
    }
    else
    {
        mImage = new Image_C(mColorFormat, mTextureSize);
        // scale texture
        origImage->copy_to_scaling(mImage);
    }
    uploadTexture(true, mipmapData);
    if(!mKeepImage)
    {
        mImage->decrement_counter();
        mImage = 0;
    }
}


//! constructor for basic setup (only for derived classes)
Texture_C::Texture_C(const std::string name, VideoManager_C* driver) :
    mColorFormat(ECF_A8R8G8B8),
    mDriver(driver),
    mImage(0),
    mMipImage(0),
    mTextureName(0),
    mInternalFormat(GL_RGBA),
    mPixelFormat(GL_BGRA_EXT),
    mPixelType(GL_UNSIGNED_BYTE),
    mHasMipMaps(true),
    mIsRenderTarget(false),
    mAutomaticMipmapUpdate(false),
    mReadOnlyLock(false),
    mKeepImage(true)
{
    //
}

//! destructor
Texture_C::~Texture_C()
{
    if(mTextureName)
        glDeleteTextures(1, &mTextureName);
    if(mImage)
        mImage->decrement_counter();
}

//! Choose best matching color format, based on texture creation flags
E_COLOR_FORMAT Texture_C::getBestColorFormat(E_COLOR_FORMAT format)
{
    E_COLOR_FORMAT destFormat = ECF_A8R8G8B8;
    switch(format)
    {
    case ECF_A1R5G5B5:
        if(!mDriver->get_texture_creation_flag(ETCF_ALWAYS_32_BIT))
            destFormat = ECF_A1R5G5B5;
        break;
    case ECF_R5G6B5:
        if(!mDriver->get_texture_creation_flag(ETCF_ALWAYS_32_BIT))
            destFormat = ECF_A1R5G5B5;
        break;
    case ECF_A8R8G8B8:
        if(mDriver->get_texture_creation_flag(ETCF_ALWAYS_16_BIT) ||
                mDriver->get_texture_creation_flag(ETCF_OPTIMIZED_FOR_SPEED))
            destFormat = ECF_A1R5G5B5;
        break;
    case ECF_R8G8B8:
        if(mDriver->get_texture_creation_flag(ETCF_ALWAYS_16_BIT) ||
                mDriver->get_texture_creation_flag(ETCF_OPTIMIZED_FOR_SPEED))
            destFormat = ECF_A1R5G5B5;
    default:
        break;
    }
    if(mDriver->get_texture_creation_flag(ETCF_NO_ALPHA_CHANNEL))
    {
        switch(destFormat)
        {
        case ECF_A1R5G5B5:
            destFormat = ECF_R5G6B5;
            break;
        case ECF_A8R8G8B8:
            destFormat = ECF_R8G8B8;
            break;
        default:
            break;
        }
    }
    return destFormat;
}

//! Get opengl values for the GPU texture storage
GLint Texture_C::getOpenGLFormatAndParametersFromColorFormat(E_COLOR_FORMAT format, GLint& filtering, GLenum& colorformat, GLenum& type)
{
    // default
    filtering = GL_LINEAR;
    colorformat = GL_RGBA;
    type = GL_UNSIGNED_BYTE;

    switch(format)
    {
    case ECF_A1R5G5B5:
        colorformat=GL_BGRA_EXT;
        type=GL_UNSIGNED_SHORT_1_5_5_5_REV;
        return GL_RGBA;
    case ECF_R5G6B5:
        colorformat=GL_BGR;
        type=GL_UNSIGNED_SHORT_5_6_5_REV;
        return GL_RGB;
    case ECF_R8G8B8:
        colorformat=GL_BGR;
        type=GL_UNSIGNED_BYTE;
        return GL_RGB;
    case ECF_A8R8G8B8:
        colorformat=GL_BGRA_EXT;
        type=GL_UNSIGNED_INT_8_8_8_8_REV;
        return GL_RGBA;
        // Floating Point texture formats. Thanks to Patryk "Nadro" Nadrowski.
    case ECF_R16F:
    {
#ifdef GL_ARB_texture_rg
        filtering = GL_NEAREST;
        colorformat = GL_RED;
        type = GL_FLOAT;

        return GL_R16F;
#else
        return GL_RGB8;
#endif
    }
    case ECF_G16R16F:
    {
#ifdef GL_ARB_texture_rg
        filtering = GL_NEAREST;
        colorformat = GL_RG;
        type = GL_FLOAT;

        return GL_RG16F;
#else
        return GL_RGB8;
#endif
    }
    case ECF_A16B16G16R16F:
    {
#ifdef GL_ARB_texture_rg
        filtering = GL_NEAREST;
        colorformat = GL_RGBA;
        type = GL_FLOAT;

        return GL_RGBA16F_ARB;
#else
        return GL_RGBA8;
#endif
    }
    case ECF_R32F:
    {
#ifdef GL_ARB_texture_rg
        filtering = GL_NEAREST;
        colorformat = GL_RED;
        type = GL_FLOAT;

        return GL_R32F;
#else
        return GL_RGB8;
#endif
    }
    case ECF_G32R32F:
    {
#ifdef GL_ARB_texture_rg
        filtering = GL_NEAREST;
        colorformat = GL_RG;
        type = GL_FLOAT;

        return GL_RG32F;
#else
        return GL_RGB8;
#endif
    }
    case ECF_A32B32G32R32F:
    {
#ifdef GL_ARB_texture_float
        filtering = GL_NEAREST;
        colorformat = GL_RGBA;
        type = GL_FLOAT;

        return GL_RGBA32F_ARB;
#else
        return GL_RGBA8;
#endif
    }
    default:
    {
        mDriver->get_log_manager()->log("Unsupported texture format", ELL_ERROR);
        return GL_RGBA8;
    }
    }
}

// prepare values ImageSize, TextureSize, and ColorFormat based on image
void Texture_C::getImageValues(Image* image)
{
    if(!image)
    {
        mDriver->get_log_manager()->log("No image for OpenGL texture.", ELL_ERROR);
        return;
    }

    mImageSize = image->get_dimension();

    if(!mImageSize.X || !mImageSize.Y)
    {
        mDriver->get_log_manager()->log("Invalid size of image for OpenGL Texture.", ELL_ERROR);
        return;
    }

    const float ratio = (float)mImageSize.X/(float)mImageSize.Y;
    if((mImageSize.X>mDriver->get_max_texture_size().X) && (ratio >= 1.0f))
    {
        mImageSize.X = mDriver->get_max_texture_size().X;
        mImageSize.Y = (uint32_t)(mDriver->get_max_texture_size().Y/ratio);
    }
    else if(mImageSize.Y>mDriver->get_max_texture_size().Y)
    {
        mImageSize.Y = mDriver->get_max_texture_size().Y;
        mImageSize.X = (uint32_t)(mDriver->get_max_texture_size().X*ratio);
    }
    mTextureSize = mImageSize.get_optimal_size(!mDriver->query_feature(EVMF_TEXTURE_NPOT));

    mColorFormat = getBestColorFormat(image->get_color_format());
}

//! copies the the texture into an open gl texture.
void Texture_C::uploadTexture(bool newTexture, void* mipmapData, uint32_t level)
{
    // check which image needs to be uploaded
    Image* image = level ? mMipImage : mImage;
    if(!image)
    {
        mDriver->get_log_manager()->log("No image for OpenGL texture to upload", ELL_ERROR);
        return;
    }

    // get correct opengl color data values
    GLenum oldInternalFormat = mInternalFormat;
    GLint filtering;
    mInternalFormat = getOpenGLFormatAndParametersFromColorFormat(mColorFormat, filtering, mPixelFormat, mPixelType);
    // make sure we don't change the internal format of existing images
    if(!newTexture)
        mInternalFormat = oldInternalFormat;

    ((VideoManager_C*)mDriver)->set_active_texture(0, this);

    // mipmap handling for main texture
    if(!level && newTexture)
    {
#ifndef DISABLE_MIPMAPPING
#ifdef GL_SGIS_generate_mipmap
        // auto generate if possible and no mipmap data is given
        if(mHasMipMaps && !mipmapData && mDriver->query_feature(EVMF_MIP_MAP_AUTO_UPDATE))
        {
            if(mDriver->get_texture_creation_flag(ETCF_OPTIMIZED_FOR_SPEED))
                glHint(GL_GENERATE_MIPMAP_HINT_SGIS, GL_FASTEST);
            else if(mDriver->get_texture_creation_flag(ETCF_OPTIMIZED_FOR_QUALITY))
                glHint(GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST);
            else
                glHint(GL_GENERATE_MIPMAP_HINT_SGIS, GL_DONT_CARE);
            // automatically generate and update mipmaps
            glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
            mAutomaticMipmapUpdate=true;
        }
        else
#endif
        {
            // Either generate manually due to missing capability
            // or use predefined mipmap data
            mAutomaticMipmapUpdate=false;
            regenerate_mip_map_levels(mipmapData);
        }
        if(mHasMipMaps)  // might have changed in regenerate_mip_map_levels
        {
            // enable bilinear mipmap filter
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        }
        else
#else
        mHasMipMaps=false;
        mDevice->get_log_manager()->log("Did not create OpenGL texture mip maps.", ELL_INFORMATION);
#endif
        {
            // enable bilinear filter without mipmaps
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        }
    }

    // now get image data and upload to GPU
    void* source = image->get_pointer();
    if(newTexture)
        glTexImage2D(GL_TEXTURE_2D, level, mInternalFormat, image->get_dimension().X, image->get_dimension().Y, 0, mPixelFormat, mPixelType, source);
    else
        glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->get_dimension().X, image->get_dimension().Y, mPixelFormat, mPixelType, source);
}

//! lock function
void* Texture_C::lock(bool readOnly, uint32_t mipmapLevel)
{
    // store info about which image is locked
    Image* image = (mipmapLevel==0) ? mImage : mMipImage;
    mReadOnlyLock |= readOnly;
    mMipLevelStored = mipmapLevel;

    // if data not available or might have changed on GPU download it
    if(!image || mIsRenderTarget)
    {
        // prepare the data storage if necessary
        if(!image)
        {
            if(mipmapLevel)
            {
                uint32_t i=0;
                uint32_t width = mTextureSize.X;
                uint32_t height = mTextureSize.Y;
                do
                {
                    if(width>1)
                        width>>=1;
                    if(height>1)
                        height>>=1;
                    ++i;
                }
                while(i != mipmapLevel);
                image = new Image_C(ECF_A8R8G8B8, Myth::Math::Vector2u(width,height));
            }
            else
                image = new Image_C(ECF_A8R8G8B8, mImageSize);
            mColorFormat = ECF_A8R8G8B8;
        }
        if(!image)
            return 0;

        uint8_t* pixels = static_cast<uint8_t*>(image->get_pointer());
        if(!pixels)
            return 0;

        // we need to keep the correct texture bound later on
        GLint tmpTexture;
        glGetIntegerv(GL_TEXTURE_BINDING_2D, &tmpTexture);
        glBindTexture(GL_TEXTURE_2D, mTextureName);

        // allows to read pixels in top-to-bottom order
#ifdef GL_MESA_pack_invert
        if(mDriver->queryOpenGLFeature(OpenGLExtensionHandler::LYNX_MESA_pack_invert))
            glPixelStorei(GL_PACK_INVERT_MESA, GL_TRUE);
#endif

        // download GPU data as ARGB8 to pixels;
        glGetTexImage(GL_TEXTURE_2D, mipmapLevel, GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels);

#ifdef GL_MESA_pack_invert
        if(mDriver->queryOpenGLFeature(OpenGLExtensionHandler::LYNX_MESA_pack_invert))
            glPixelStorei(GL_PACK_INVERT_MESA, GL_FALSE);
        else
#endif
        {
            // opengl images are horizontally flipped, so we have to fix that here.
            const int32_t pitch=image->get_pitch();
            uint8_t* p2 = pixels + (image->get_dimension().Y - 1) * pitch;
            uint8_t* tmpBuffer = new uint8_t[pitch];
            for(uint32_t i=0; i < image->get_dimension().Y; i += 2)
            {
                memcpy(tmpBuffer, pixels, pitch);
                memcpy(pixels, p2, pitch);
                memcpy(p2, tmpBuffer, pitch);
                pixels += pitch;
                p2 -= pitch;
            }
            delete [] tmpBuffer;
        }

        //reset old bound texture
        glBindTexture(GL_TEXTURE_2D, tmpTexture);
    }
    return image->get_pointer();
}


//! unlock function
void Texture_C::unlock()
{
    // test if miplevel or main texture was locked
    Image* image = mMipImage ? mMipImage : mImage;
    if(!image)
        return;
    // copy texture data to GPU
    if(!mReadOnlyLock)
        uploadTexture(false, 0, mMipLevelStored);
    mReadOnlyLock = false;
    // cleanup local image
    if(mMipImage)
    {
        mMipImage->decrement_counter();
        mMipImage=0;
    }
    else if(!mKeepImage)
    {
        mImage->decrement_counter();
        mImage=0;
    }
    // update information
    if(mImage)
        mColorFormat = mImage->get_color_format();
    else
        mColorFormat = ECF_A8R8G8B8;
}

//! Returns size of the original image.
const Myth::Math::Vector2u Texture_C::get_original_size() const
{
    return mImageSize;
}

//! Returns size of the texture.
const Myth::Math::Vector2u Texture_C::get_size() const
{
    return mTextureSize;
}

//! returns color format of texture
E_COLOR_FORMAT Texture_C::get_color_format() const
{
    return mColorFormat;
}

//! returns pitch of texture (in bytes)
uint32_t Texture_C::get_pitch() const
{
    if(mImage)
        return mImage->get_pitch();
    else
        return 0;
}

bool Texture_C::has_alpha() const
{
    return get_color_format() == ECF_A8R8G8B8 || get_color_format() == ECF_A1R5G5B5;
}

const std::string Texture_C::get_name() const
{
    return mName;
}

void Texture_C::set_name(std::string name)
{
    mName = name;
}

void Texture_C::make_color_key_texture(Color color, bool zeroTexels)
{

    if(get_color_format() != ECF_A1R5G5B5 &&
            get_color_format() != ECF_A8R8G8B8)
    {
        mDriver->get_log_manager()->log("Error: Unsupported texture color format for making color key channel.", ELL_ERROR);
        return;
    }

    if(get_color_format() == ECF_A1R5G5B5)
    {
        uint16_t* p = (uint16_t*)lock();

        if(!p)
        {
            mDriver->get_log_manager()->log("Could not lock texture for making color key channel.", ELL_ERROR);
            return;
        }

        const Myth::Math::Vector2u dim = get_size();
        const uint32_t pitch = get_pitch() / 2;

        // color with alpha disabled (i.e. fully transparent)
        const uint16_t refZeroAlpha = (0x7fff & color.to_a1r5g5b5());

        const uint32_t pixels = pitch * dim.Y;

        for(uint32_t pixel = 0; pixel < pixels; ++ pixel)
        {
            // If the colour matches the reference colour, ignoring alphas,
            // set the alpha to zero.
            if(((*p) & 0x7fff) == refZeroAlpha)
            {
                if(zeroTexels)
                    (*p) = 0;
                else
                    (*p) = refZeroAlpha;
            }

            ++p;
        }

        unlock();
    }
    else
    {
        uint32_t* p = (uint32_t*)lock();

        if(!p)
        {
            mDriver->get_log_manager()->log("Could not lock texture for making color key channel.", ELL_ERROR);
            return;
        }

        Myth::Math::Vector2u dim = get_size();
        uint32_t pitch = get_pitch() / 4;

        // color with alpha disabled (fully transparent)
        const uint32_t refZeroAlpha = 0x00ffffff & color.mColor;

        const uint32_t pixels = pitch * dim.Y;
        for(uint32_t pixel = 0; pixel < pixels; ++ pixel)
        {
            // If the colour matches the reference colour, ignoring alphas,
            // set the alpha to zero.
            if(((*p) & 0x00ffffff) == refZeroAlpha)
            {
                if(zeroTexels)
                    (*p) = 0;
                else
                    (*p) = refZeroAlpha;
            }

            ++p;
        }

        unlock();
    }
}

void Texture_C::make_color_key_texture(Myth::Math::Vector2i colorKeyPixelPos, bool zeroTexels)
{
    if(get_color_format() != ECF_A1R5G5B5 &&
            get_color_format() != ECF_A8R8G8B8)
    {
        mDriver->get_log_manager()->log("Error: Unsupported texture color format for making color key channel.", ELL_ERROR);
        return;
    }

    Color colorKey;

    if(get_color_format() == ECF_A1R5G5B5)
    {
        uint16_t* p = (uint16_t*)lock(true);

        if(!p)
        {
            mDriver->get_log_manager()->log("Could not lock texture for making color key channel.", ELL_ERROR);
            return;
        }

        uint32_t pitch = get_pitch() / 2;

        const uint16_t key16Bit = 0x7fff & p[colorKeyPixelPos.Y*pitch + colorKeyPixelPos.X];

        colorKey = a1r5g5b5_to_a8r8g8b8(key16Bit);
    }
    else
    {
        uint32_t* p = (uint32_t*)lock(true);

        if(!p)
        {
            mDriver->get_log_manager()->log("Could not lock texture for making color key channel.", ELL_ERROR);
            return;
        }

        uint32_t pitch = get_pitch() / 4;
        colorKey = 0x00ffffff & p[colorKeyPixelPos.Y*pitch + colorKeyPixelPos.X];
    }

    unlock();
    make_color_key_texture(colorKey, zeroTexels);

}

void Texture_C::make_normal_map_texture(float amplitude)
{
    if(get_color_format() != ECF_A1R5G5B5 &&
            get_color_format() != ECF_A8R8G8B8)
    {
        mDriver->get_log_manager()->log("Error: Unsupported texture color format for making normal map.", ELL_ERROR);
        return;
    }

    Myth::Math::Vector2u dim = get_size();
    amplitude = amplitude / 255.0f;
    float vh = dim.Y / (float)dim.X;
    float hh = dim.X / (float)dim.Y;

    if(get_color_format() == ECF_A8R8G8B8)
    {
        // ECF_A8R8G8B8 version

        int32_t* p = (int32_t*)lock();

        if(!p)
        {
            mDriver->get_log_manager()->log("Could not lock texture for making normal map.", ELL_ERROR);
            return;
        }

        // copy texture

        uint32_t pitch = get_pitch() / 4;

        int32_t* in = new int32_t[dim.Y * pitch];
        memcpy(in, p, dim.Y * pitch * 4);

        for(int32_t x=0; x < int32_t(pitch); ++x)
            for(int32_t y=0; y < int32_t(dim.Y); ++y)
            {
                // TODO: this could be optimized really a lot

                Myth::Math::Vector3f h1((x-1)*hh, nml32(x-1, y, pitch, dim.Y, in)*amplitude, y*vh);
                Myth::Math::Vector3f h2((x+1)*hh, nml32(x+1, y, pitch, dim.Y, in)*amplitude, y*vh);
                //Myth::Math::vector3df v1(x*hh, nml32(x, y-1, pitch, dim.Height, in)*amplitude, (y-1)*vh);
                //Myth::Math::vector3df v2(x*hh, nml32(x, y+1, pitch, dim.Height, in)*amplitude, (y+1)*vh);
                Myth::Math::Vector3f v1(x*hh, nml32(x, y+1, pitch, dim.Y, in)*amplitude, (y-1)*vh);
                Myth::Math::Vector3f v2(x*hh, nml32(x, y-1, pitch, dim.Y, in)*amplitude, (y+1)*vh);

                Myth::Math::Vector3f v = v1-v2;
                Myth::Math::Vector3f h = h1-h2;

                Myth::Math::Vector3f n = v.cross_product(h);
                n.normalize();
                n *= 0.5f;
                n += Myth::Math::Vector3f(0.5f,0.5f,0.5f); // now between 0 and 1
                n *= 255.0f;

                int32_t height = (int32_t)nml32(x, y, pitch, dim.Y, in);
                p[y* pitch + x] = Color(
                                      height, // store height in alpha
                                      (int32_t)n.X, (int32_t)n.Z, (int32_t)n.Y).mColor;
            }

        delete [] in;
        unlock();
    }
    else
    {
        // ECF_A1R5G5B5 version

        int16_t* p = (int16_t*)lock();

        if(!p)
        {
            mDriver->get_log_manager()->log("Could not lock texture for making normal map.", ELL_ERROR);
            return;
        }

        uint32_t pitch = get_pitch() / 2;

        // copy texture

        int16_t* in = new int16_t[dim.Y * pitch];
        memcpy(in, p, dim.Y * pitch * 2);

        for(int32_t x=0; x < int32_t(pitch); ++x)
            for(int32_t y=0; y < int32_t(dim.Y); ++y)
            {
                // TODO: this could be optimized really a lot

                Myth::Math::Vector3f h1((x-1)*hh, nml16(x-1, y, pitch, dim.Y, in)*amplitude, y*vh);
                Myth::Math::Vector3f h2((x+1)*hh, nml16(x+1, y, pitch, dim.Y, in)*amplitude, y*vh);
                Myth::Math::Vector3f v1(x*hh, nml16(x, y-1, pitch, dim.Y, in)*amplitude, (y-1)*vh);
                Myth::Math::Vector3f v2(x*hh, nml16(x, y+1, pitch, dim.Y, in)*amplitude, (y+1)*vh);

                Myth::Math::Vector3f v = v1-v2;
                Myth::Math::Vector3f h = h1-h2;

                Myth::Math::Vector3f n = v.cross_product(h);
                n.normalize();
                n *= 0.5f;
                n += Myth::Math::Vector3f(0.5f,0.5f,0.5f); // now between 0 and 1
                n *= 255.0f;

                p[y* pitch + x] = rgba16((uint32_t)n.X, (uint32_t)n.Z, (uint32_t)n.Y);
            }

        delete [] in;
        unlock();
    }

    regenerate_mip_map_levels();
}

//! return open gl texture name
GLuint Texture_C::getOpenGLTextureName() const
{
    return mTextureName;
}

//! Returns whether this texture has mipmaps
bool Texture_C::has_mip_maps() const
{
    return mHasMipMaps;
}

//! Regenerates the mip map levels of the texture. Useful after locking and
//! modifying the texture
void Texture_C::regenerate_mip_map_levels(void* mipmapData)
{
    if(mAutomaticMipmapUpdate || !mHasMipMaps || !mImage)
        return;
    if((mImage->get_dimension().X==1) && (mImage->get_dimension().Y==1))
        return;

    // Manually create mipmaps or use prepared version
    uint32_t width=mImage->get_dimension().X;
    uint32_t height=mImage->get_dimension().Y;
    uint32_t i=0;
    uint8_t* target = static_cast<uint8_t*>(mipmapData);
    do
    {
        if(width>1)
            width>>=1;
        if(height>1)
            height>>=1;
        ++i;
        if(!target)
            target = new uint8_t[width*height*mImage->get_bytes_per_pixel()];
        // create scaled version if no mipdata available
        if(!mipmapData)
            mImage->copy_to_scaling(target, width, height, mImage->get_color_format());
        glTexImage2D(GL_TEXTURE_2D, i, mInternalFormat, width, height,
                     0, mPixelFormat, mPixelType, target);
        // get next prepared mipmap data if available
        if(mipmapData)
        {
            mipmapData = static_cast<uint8_t*>(mipmapData)+width*height*mImage->get_bytes_per_pixel();
            target = static_cast<uint8_t*>(mipmapData);
        }
    }
    while(width!=1 || height!=1);
    // cleanup
    if(!mipmapData)
        delete [] target;
}


bool Texture_C::is_render_target() const
{
    return mIsRenderTarget;
}


void Texture_C::setIsRenderTarget(bool isTarget)
{
    mIsRenderTarget = isTarget;
}


bool Texture_C::isFrameBufferObject() const
{
    return false;
}


//! Bind Render Target Texture
void Texture_C::bindRTT()
{
}


//! Unbind Render Target Texture
void Texture_C::unbindRTT()
{
    mDriver->set_active_texture(0, this);

    // Copy Our ViewPort To The Texture
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, get_size().X, get_size().Y);
}


/* FBO Textures */

// helper function for render to texture
static bool checkFBOStatus(VideoManager_C* Manager);

//! RTT ColorFrameBuffer constructor
TextureFBO_C::TextureFBO_C(const Myth::Math::Vector2u size, const std::string name, VideoManager_C* driver, const E_COLOR_FORMAT format) :
    Texture_C(name, driver),
    mDepthTexture(0),
    mColorFrameBuffer(0)
{
    mImageSize = size;
    mTextureSize = size;

    GLint mFilteringType;
    mInternalFormat = getOpenGLFormatAndParametersFromColorFormat(format, mFilteringType, mPixelFormat, mPixelType);

    mHasMipMaps = false;
    mIsRenderTarget = true;

#ifdef GL_EXT_framebuffer_object
    // generate frame buffer
    mDriver->extGlGenFramebuffers(1, &mColorFrameBuffer);
    mDriver->extGlBindFramebuffer(GL_FRAMEBUFFER_EXT, mColorFrameBuffer);

    // generate color texture
    glGenTextures(1, &mTextureName);
    mDriver->set_active_texture(0, this);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mFilteringType);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, mInternalFormat, mImageSize.X,
                 mImageSize.Y, 0, mPixelFormat, mPixelType, 0);

    // attach color texture to frame buffer
    mDriver->extGlFramebufferTexture2D(GL_FRAMEBUFFER_EXT,
                                       GL_COLOR_ATTACHMENT0_EXT,
                                       GL_TEXTURE_2D,
                                       mTextureName,
                                       0);
#endif
    unbindRTT();
}

//! destructor
TextureFBO_C::~TextureFBO_C()
{
    if(mDepthTexture)
        if(mDepthTexture->decrement_counter())
            mDriver->remove_depth_texture(mDepthTexture);
    if(mColorFrameBuffer)
        mDriver->extGlDeleteFramebuffers(1, &mColorFrameBuffer);
}


bool TextureFBO_C::isFrameBufferObject() const
{
    return true;
}

//! Bind Render Target Texture
void TextureFBO_C::bindRTT()
{
#ifdef GL_EXT_framebuffer_object
    if(mColorFrameBuffer != 0)
        mDriver->extGlBindFramebuffer(GL_FRAMEBUFFER_EXT, mColorFrameBuffer);
#endif
}


//! Unbind Render Target Texture
void TextureFBO_C::unbindRTT()
{
#ifdef GL_EXT_framebuffer_object
    if(mColorFrameBuffer != 0)
        mDriver->extGlBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
#endif
}


/* FBO Depth Textures */

//! RTT DepthBuffer constructor
TextureFBODepth_C::TextureFBODepth_C(const Myth::Math::Vector2u size, const std::string name, VideoManager_C* driver, bool useStencil) :
    TextureFBO_C(size, name, driver),
    mDepthRenderBuffer(0),
    mStencilRenderBuffer(0),
    mUseStencil(useStencil)
{
    mImageSize = size;
    mTextureSize = size;
    mInternalFormat = GL_RGBA;
    mPixelFormat = GL_RGBA;
    mPixelType = GL_UNSIGNED_BYTE;
    mHasMipMaps = false;

    if(useStencil)
    {
        glGenTextures(1, &mDepthRenderBuffer);
        glBindTexture(GL_TEXTURE_2D, mDepthRenderBuffer);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
#ifdef GL_EXT_packed_depth_stencil
        if(mDriver->queryOpenGLFeature(OpenGLExtensionHandler::LYNX_EXT_packed_depth_stencil))
        {
            // generate packed depth stencil texture
            glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL_EXT, mImageSize.X,
                         mImageSize.Y, 0, GL_DEPTH_STENCIL_EXT, GL_UNSIGNED_INT_24_8_EXT, 0);
            mStencilRenderBuffer = mDepthRenderBuffer; // stencil is packed with depth
        }
        else // generate separate stencil and depth textures
#endif
        {
            // generate depth texture
            glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, mImageSize.X,
                         mImageSize.Y, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);

        }
    }
#ifdef GL_EXT_framebuffer_object
    else
    {
        // generate depth buffer
        mDriver->extGlGenRenderbuffers(1, &mDepthRenderBuffer);
        mDriver->extGlBindRenderbuffer(GL_RENDERBUFFER_EXT, mDepthRenderBuffer);
        mDriver->extGlRenderbufferStorage(GL_RENDERBUFFER_EXT,
                                          GL_DEPTH_COMPONENT, mImageSize.X,
                                          mImageSize.Y);
    }
#endif
}


//! destructor
TextureFBODepth_C::~TextureFBODepth_C()
{
    if(mDepthRenderBuffer && mUseStencil)
        glDeleteTextures(1, &mDepthRenderBuffer);
    else
        mDriver->extGlDeleteRenderbuffers(1, &mDepthRenderBuffer);
    if(mStencilRenderBuffer && mStencilRenderBuffer != mDepthRenderBuffer)
        glDeleteTextures(1, &mStencilRenderBuffer);
}


//combine depth texture and rtt
bool TextureFBODepth_C::attach(Texture* renderTex)
{
    if(!renderTex)
        return false;
    TextureFBO_C* rtt = static_cast<TextureFBO_C*>(renderTex);
    rtt->bindRTT();
#ifdef GL_EXT_framebuffer_object
    if(mUseStencil)
    {
        // attach stencil texture to stencil buffer
        mDriver->extGlFramebufferTexture2D(GL_FRAMEBUFFER_EXT,
                                           GL_STENCIL_ATTACHMENT_EXT,
                                           GL_TEXTURE_2D,
                                           mStencilRenderBuffer,
                                           0);

        // attach depth texture to depth buffer
        mDriver->extGlFramebufferTexture2D(GL_FRAMEBUFFER_EXT,
                                           GL_DEPTH_ATTACHMENT_EXT,
                                           GL_TEXTURE_2D,
                                           mDepthRenderBuffer,
                                           0);
    }
    else
    {
        // attach depth renderbuffer to depth buffer
        mDriver->extGlFramebufferRenderbuffer(GL_FRAMEBUFFER_EXT,
                                              GL_DEPTH_ATTACHMENT_EXT,
                                              GL_RENDERBUFFER_EXT,
                                              mDepthRenderBuffer);
    }
#endif
    // check the status
    if(!checkFBOStatus(mDriver))
    {
        mDriver->get_log_manager()->log("FBO incomplete");
        return false;
    }
    rtt->mDepthTexture=this;
    increment_counter(); // grab the depth buffer, not the RTT
    rtt->unbindRTT();
    return true;
}

//! Bind Render Target Texture
void TextureFBODepth_C::bindRTT()
{
}

//! Unbind Render Target Texture
void TextureFBODepth_C::unbindRTT()
{
}

bool checkFBOStatus(VideoManager_C* Manager)
{
#ifdef GL_EXT_framebuffer_object
    GLenum status = Manager->extGlCheckFramebufferStatus(GL_FRAMEBUFFER_EXT);

    switch(status)
    {
        //Our FBO is perfect, return true
    case GL_FRAMEBUFFER_COMPLETE_EXT:
        return true;

    case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
        Manager->get_log_manager()->log("FBO has invalid read buffer", ELL_ERROR);
        break;

    case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
        Manager->get_log_manager()->log("FBO has invalid draw buffer", ELL_ERROR);
        break;

    case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
        Manager->get_log_manager()->log("FBO has one or several incomplete image attachments", ELL_ERROR);
        break;

    case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
        Manager->get_log_manager()->log("FBO has one or several image attachments with different internal formats", ELL_ERROR);
        break;

    case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
        Manager->get_log_manager()->log("FBO has one or several image attachments with different dimensions", ELL_ERROR);
        break;

// not part of fbo_object anymore, but won't harm as it is just a return value
#ifdef GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT
    case GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT:
        Manager->get_log_manager()->log("FBO has a duplicate image attachment", ELL_ERROR);
        break;
#endif

    case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
        Manager->get_log_manager()->log("FBO missing an image attachment", ELL_ERROR);
        break;

#ifdef GL_EXT_framebuffer_multisample
    case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT:
        Manager->get_log_manager()->log("FBO wrong multisample setup", ELL_ERROR);
        break;
#endif

    case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
        Manager->get_log_manager()->log("FBO format unsupported", ELL_ERROR);
        break;

    default:
        break;
    }
#endif
    Manager->get_log_manager()->log("FBO error", ELL_ERROR);
    return false;
}

} // namespace Lynx
